<?php

/**
 * @copyright Michiel Hakvoort 2010
 * @license http://www.opensource.org/licenses/bsd-license.php New BSD
 * @package mangrove
 * @subpackage grove
 * @filesource
 */

/*
 * Copyright (c) 2010 Michiel Hakvoort
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 * 3. The name of the author may not be used to endorse or promote products
 *    derived from this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
 * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
 * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
 * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
 * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
 * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 *
 */

namespace mg;

class Path {

    private $path = null;

    public function __construct(Route $route) {
        $this->path = '/' . trim($route->getPath(), '/');
    }

    public function getPath() {
        return $this->path;
    }

    public function __toString() {
        return $this->path;
    }

    public static function resolveInstance() {
        return new Path(get('mg\Route'));
    }
}

interface Router {

    /**
     *
     * @param HttpRequest $request
     * @return Route
     */
    public function route(HttpRequest $httpRequest);
    public function routeLiteral($literal, array $actionParameters = array(), $isInternal = true, $isConditional = true);

    public function unroute(HttpRequest $httpRequest, Route $route);


}

class DefaultRouter implements Router {

    public function route(HttpRequest $httpRequest) {
        $pathInfo = $httpRequest->getPathInfo();
        $actionParameters = $httpRequest->getQueryParameters();

        return $this->routeLiteral($pathInfo, $actionParameters);
    }

    public function routeLiteral($pathInfo, array $actionParameters = array(), $isInternal = true, $isConditional = true) {
        $application = null;
        $path = null;
        $module = null;
        $action = null;

        if($pathInfo !== null) {

            $inAction = false;
            $components = explode('/', $pathInfo);

            // first get the application (from the front)
            while(count($components) > 0 && $application === null && !$inAction) {
                $part = array_shift($components);

                if($part === '') { continue; };

                if($part === '-') {
                    $inAction = true;
                } else {
                    $application = $part;
                }
            }

            $this->path = '';

            // collect everything after the application until a dash as context
            while(count($components) > 0 && !$inAction) {
                $part = array_shift($components);
                if($part === '') { continue; };

                if($part === '-') {
                    $inAction = true;
                } else {
                    $path .= ($path !== '' ? '/' : '') . $part;
                }
            }

            // then the action (from the back)
            while(count($components) > 0 && $action === null) {
                $part = array_pop($components);
                if($part === '') { continue; };

                $action = $part;
            }

            // module (from the front)
            while(count($components) > 0 && $module === null) {
                $part = array_shift($components);
                if($part === '') { continue; };

                $module = $part;
            }

        }

        if($isInternal) {
            return new InternalRoute($application, $path, $module, $action, $actionParameters, $isConditional);
        } else {
            return new ExternalRoute($application, $path, $module, $action, $actionParameters);
        }

    }

    public function unroute(HttpRequest $httpRequest, Route $route) {
        $url = 'http';
        if($httpRequest->isSecure()) {
            $url .= 's';
        }
        $url .= '://';

        $url .= $httpRequest->getDomain();

        if($httpRequest->getPort() !== 80) {
            $url .= ':' . $httpRequest->getPort();
        }

        if(!$httpRequest->isRewritten()) {
            $url .= $httpRequest->getScriptName();
        }

        if($route->getApplication() !== null) {
            $url .= '/' . $route->getApplication();
        }

        if($route->getPath() !== null && $route->getApplication() !== null) {
            $url .= '/' . trim($route->getPath(), '/');
        }

        if($route->getAction() !== null) {
            $url .= '/-';
        }

        if($route->getModule() !== null) {
            $url .= '/' . $route->getModule();
        }

        if($route->getAction() !== null) {
            $url .= '/' . $route->getAction();
        }

        $actionParameters = $route->getActionParameters();

        if($route->getAction() !== null && count($actionParameters) > 0) {
            $url .= '?' . http_build_query($actionParameters);
        }

        return $url;
    }
}

class Route {

    protected $application = null;
    protected $module = null;
    protected $path = null;
    protected $action;
    protected $actionParameters = null;
    protected $isInternal = true;
    protected $isConditional = true;

    public function __construct($application, $path, $module, $action, array $actionParameters, $isInternal, $isConditional) {
        $this->application = $application;
        $this->path = $path;
        $this->module = $module;
        $this->action = $action;
        $this->actionParameters = $actionParameters ?: array();
        $this->isInternal = $isInternal;
        $this->isConditional = $isConditional;
    }

    /**
     * @return string
     */
    public function getApplication() {
        return $this->application;
    }

    /**
     * @return string
     */
    public function getModule() {
        return $this->module;
    }

    /**
     * @return string
     */
    public function getAction() {
        return $this->action;
    }

    /**
     * @return string
     */
    public function getPath() {
        return $this->path;
    }

    public function getActionParameters() {
        return $this->actionParameters;
    }

    public function isInternal() {
        return $this->isInternal;
    }

    public function isConditional() {
        return $this->isConditional;
    }

    /**
     *
     * @param $target
     *
     * @return \mg\MutableRoute
     */
    public static function to($target = null) {
        $route = null;

        if($target !== null) {
            $route = in(function(Router $router) use($target) {
                if(is_string($target)) {
                    return $router->routeLiteral($target);
                } elseif($target instanceof Route) {
                    /* @var $route \mg\Route */
                    return new InternalRoute($target->getApplication(), $target->getPath(), $target->getModule(), $target->getAction(), $target->getActionParameters(), true);
                }
            });
        } else {
            $route = new InternalRoute(null, null, null, null, array(), true);
        }

        return $route;
    }
}

class ExternalRoute extends Route {

    public function __construct($application, $path, $module, $action, array $actionParameters) {
        parent :: __construct($application, $path, $module, $action, $actionParameters, false, true);
    }

    public static function resolveInstance() {
        return in(function(\mg\Router $router, \mg\HttpRequest $request) {
            return $router->route($request);
        });
    }
}


abstract class MutableRoute extends Route {

    protected $currentAction = null;

    public function __construct($application, $path, $module, $action, array $actionParameters, $isInternal, $isConditional) {
        parent :: __construct($application, $path, $module, $action, $actionParameters, $isInternal, $isConditional);

        if($action !== null) {
            if($module !== null) {
                $this->currentAction = 'module';
            } else {
                $this->currentAction = 'application';
            }
        }
    }

    /**
     * @return \mg\MutableRoute
     */
    public function m($module, $action) {
        return $this->module($module, $action);
    }

    /**
     * @return \mg\MutableRoute
     */
    public function setConditional($isConditional) {
        $this->isConditional = $isConditional;
        return $this;
    }

    /**
     * @return \mg\MutableRoute
     */
    public function module($module, $action) {
        $this->action = $action;
        $this->module = $module;
        $this->currentAction = 'module';

        $this->actionParameters = array();

        return $this;
    }

    /**
     * @return \mg\MutableRoute
     */
    public function p($path = null) {
        return $this->path($path);
    }

    /**
     * @return \mg\MutableRoute
     */
    public function path($path = null) {
        $this->path = $path;
        return $this;
    }

    /**
     * @return \mg\MutableRoute
     */
    public function a($application, $action = null) {
        return $this->application($application, $action);
    }

    /**
     * @return \mg\MutableRoute
     */
    public function application($application, $action = null) {
        if($this->currentAction === 'application') {
            $this->action = null;
            $this->actionParameters = array();
        }

        if($action !== null) {
            $this->currentAction = 'application';
            $this->action = $action;
        }

        $this->application = $application;

        return $this;
    }

    /**
     * @return \mg\MutableRoute
     */
    public function param($name, $value = null) {
        if($value === null && isset($this->actionParameters[$name])) {
            unset($this->actionParameters[$name]);
        } else {
            $this->actionParameters[$name] = $value;
        }

        return $this;
    }

}

class InternalRoute extends MutableRoute {

    public function __construct($application, $path, $module, $action, array $actionParameters, $isConditional) {
        parent :: __construct($application, $path, $module, $action, $actionParameters, true, $isConditional);
    }
}
